<?php

/**
* yvBBCode - BBCode Plugin, developed for Joomla! 1.5
* @version		$Id: yvbbcode.php 0004 2008-06-16 00:00:00Z yvolk $
* @package		yvBBCodePlugin
* @copyright	2007-2008 yvolk (Yuri Volkov), http://yurivolkov.com. All rights reserved.
* @license		GNU/GPL, see LICENSE.php
*/

// no direct access
defined('_JEXEC') or die('Restricted access');

define('yvBBCodeVersion', '1.2.0');
define('JPATH_SITE_YVBBCODE', dirname(__FILE__));
define('JPATH_SITE_YVBBCODE_ASSETS', JPATH_SITE_YVBBCODE . DS . 'yvbbcode' );
// 2008-08-10 yvolk changed require_once to jimport 
//require_once JPATH_SITE . DS . 'libraries'  . DS . 'pear' . DS . 'PEAR.php';
jimport('pear.PEAR');
require_once (JPATH_SITE_YVBBCODE_ASSETS . DS . 'BBCodeParser.php');
require_once (JPATH_SITE_YVBBCODE_ASSETS . DS . 'BBCodeParser' . DS .  'Filter.php');

class plgSystemyvbbcode extends JPlugin {
	var $_initialized = false;
	var $_instanceId = 0;

	var $_debug = false;
	var $_bbcodeset_path = '';
	// Groups of BBCodes (as defined in BBCodeParser...)
	var $_filters = array ();
	var $_abbcode = array ();
	//Array with sorted _abbcode for replacements 
	var $_abbcodeHeader = array ();
	var $_parser = null;

	/**
	 * Let's support PHP4 awhile...
	 * Returns true for PHP4 
	 */
  function _PHP4() {
		if (version_compare( phpversion(), '5.0' ) < 0) {
			if (!$this->_initialized) {
			  //Something is wrong with PHP4... let's repeat the work...	
				$this->_instanceId = rand(1000, 9999);
				$this->_initialize();
			}
			return true;
		} else {
		  return false;
		}  		
  }

	/**
	 * Constructor
	 */
	function plgSystemYvbbcode(& $subject, $config) {
		parent :: __construct($subject, $config);
		$this->_instanceId = rand(1000, 9999);
		//echo 'yvBBCode ' . $this->_instanceId . ' created.<br/>';
	}

	// The error handler is not used yet... maybe some time later :-)
	//	function _yvBBCode_errorHandler($errcode, $errstring, $filename=null, $lineno=null, $context=null) {
	//		global $mainframe;
	//		$mainframe->enqueueMessage('errcode=' .$errcode . '; errstring="' . $errstring , '"', 'error');
	//	}

	function onAfterRoute() {
		$this->_initialize();
	}

	// Show clickable BBCodes
	function onBBCode_RenderForm($id_of_target_control, & $Form) {
		$this->_PHP4();	
		return $this->_renderForm($id_of_target_control, $Form);
	}

	// Replace codes of BBCodes with HTML tags
	function onBBCode_RenderText(& $string_to_process) {
		$this->_PHP4();	
		$results = $this->_replaceCode($string_to_process);
	}


	function onPrepareContent(& $article, & $params, $page = 0) {
		$this->_PHP4();	
		if ($this->params->get('articletext', 0)) {
			if ($this->_articleSectionEnabled($article->sectionid)) {
				$this->_replaceCode($article->text);
			}
		}
		return true;
	}

	// Do BBCode replacements on the whole page
	function onAfterRender() {
		$this->_PHP4();
		if ($this->params->get('pagetext', 0)) {
			$document = & JFactory :: getDocument();
			$doctype = $document->getType();
			if ($doctype == 'html') {
				$body = JResponse :: getBody();
				if ($this->_replaceCode($body)) {
					//$body .= 'yvBBCode - ' . $doctype . '<br/>';
					JResponse :: setBody($body);
				}
			}
		}
	}

	// returns boolean Ok
	function _initialize() {
		global $mainframe;
		if ($this->_initialized) {
			JError::raiseWarning( '1' , 'yvBBCode instanceId=' . $this->_instanceId . ' was initialized already');
			return true;
		}
		//echo 'yvBBCode ' . $this->_instanceId . ' initializes...<br/>';

		$this->_initialized = true;
		$document = & JFactory :: getDocument();
		$doctype = $document->getType();

		// Only render for HTML output
		if ($doctype !== 'html') {
			return false;
		}

		$Ok = true;
		$message = '';
		$path = '';
		$url = '';
		$bbcodeset_baseurl = '';

		$this->loadLanguage('plg_system_yvbbcode', JPATH_ADMINISTRATOR);
//		$lang = & JFactory :: getLanguage();
//		$lang->load('plg_system_yvbbcode', JPATH_ADMINISTRATOR);

		$this->_debug = $this->_getConfigValue('debug', '0');
		$path = $this->_getConfigValue('bbcodeset_path', 'yvbbcode/editor/default.xml');
		//echo 'bbcodeset_path="' . $path . '"<br />';
		$path = str_replace('/', DS, $path);
		$url = str_replace('\\', '/', $path);

		if (substr($path, 0, 8) == 'yvbbcode') {
			// path, relative from this file	  	
			$pos = strrpos($url, '/');
			if ($pos > 0) {
				$bbcodeset_baseurl = 'plugins/system/' . substr($url, 0, $pos +1);
			}
			$path = JPATH_SITE_YVBBCODE . DS . $path;
		} else
			if (substr($url, 0, 1) == '/') {
				// starts with '/' - this is path, relative from site root	
				$url = substr($url, 1);
				$pos = strrpos($url, '/');
				if ($pos > 0) {
					$bbcodeset_baseurl = substr($url, 0, $pos +1);
				}
				$path = JPATH_SITE . $path;
			}
		if (!file_exists($path)) {
			$Ok = false;
			$message .= 'The BBCode set file doesn\'t exist="' . $path . '"<br />';
		} else {
			$this->_bbcodeset_path = $path;
			$Ok = $this->_readBBCodeSet($message, $bbcodeset_baseurl);
		}

		$options = array();
		$options['filters'] = $this->_filters;
		
		// initialize BBCode parser
		$this->_parser = new HTML_BBCodeParser($options);

		if ($this->_getConfigValue('usedefaultcss', '1')) {
			$document->addStyleSheet($this->_getSiteURL() . 'plugins/system/yvbbcode/default.css', 'text/css');
		}
		$document->addScript($this->_getSiteURL() . 'plugins/system/yvbbcode/default.js', 'text/javascript');
		if (!$Ok) {
			 $this->_debug = false;
		}
		if ($this->_debug) {
			//global $mainframe;
			$mainframe->enqueueMessage($this->_TestMe(), 'notice');
		}

		if (strlen($message) > 0) {
			$message .= 'BBCode set file="' . $this->_bbcodeset_path . '"';
			$message .= $this->_textSignature();
			$mainframe->enqueueMessage($message, ($Ok ? 'notice' : 'error'));
		}
		return ($Ok);
	}
	
	// Test loaded BBCodes
	// Returns HTML string - result of test
	function _TestMe() {
		$ControlID = 'document.forms.yvBBCodeDebugForm' . $this->_instanceId . '.fulltext';

		$strOut = '<h3>yvBBCode test results</h3>';
		$strOut .= '1. Try to insert BBCodes in a Form:<br />';
		$this->_renderForm($ControlID, $strOut);
		$strOut .= '<form id=\'yvBBCodeDebugForm' . $this->_instanceId . '\' >';
		$strOut .= '<textarea id=\'fulltext\' rows=\'3\' cols=\'80\' >something</textarea><br />';

		$strOut .= '<hr>2. Test replacements of BBCodes:<br />';
		$nRows = 0;
		$src = '';
		$result = '';
		foreach ($this->_abbcode as $row) {
		  $nRows += 1;			
			$tag = $row[0];
			$test = $row[3];
			$src .= $tag . ': ' . $test . chr(10);
			if ($this->_replaceCode($test)) {
				$result .= $tag . ': ' . $test . '<br />'; 
			} 
		}
		if ($nRows>0) {
			$strOut .= '<textarea id=\'src1\' rows=\'' . $nRows . '\' cols=\'80\' >' . $src . '</textarea><br />';
		}
		$strOut .= '</form>';
		$strOut .= $result;
		$strOut .= '<input size=45 value="[b]BBCode inside input is not changed[/b]"/><br />';
		$strOut .= 'BBCode set file=\'' . $this->_bbcodeset_path . '\'<br />';
		$strOut .= $this->_textSignature();
		
		return $strOut; 
	}

	function _readBBCodeSet(& $message, $bbcodeset_baseurl = '') {
		global $mainframe;
		$Ok = true;

		jimport('joomla.utilities.simplexml');

		$xml = new JSimpleXML();
		$xml->loadFile($this->_bbcodeset_path);
		if (!$xml->document) {
			$message .= 'Error loading BBCodeSet file<br />';
			$Ok = false;
		}

		// Create list of filters
		// These filters should be also defined in /BBCodeParser/Filter folder
		if ($Ok) {
			$filters = null;
			// (PHP 5 >= 5.1.0)		
			//if (property_exists($xml->document, 'bbcodes')) {
			if (isset($xml->document->filters)) {
				$filters = $xml->document->filters[0];
			}		
			if (!$filters) {
				$message .= '"filters" element not found in BBCodeSet file ?? Maybe this is wrong XML file?<br />';
				$Ok = false;
			}
		}
		if ($Ok) {
			foreach ($filters->children() as $child) {
				//print_r($child);	
				$filter = trim($child->data());
				if ($this->_getConfigValue('filter_' . $filter, '0')) {
					$this->_filters[] = $filter;
				}
			} // foreach
			//$message .= 'Filters: ' . print_r($this->_filters, true) . '<br />';
		}

		if ($Ok) {
			$bbcodes = null;
			// (PHP 5 >= 5.1.0)		
			//if (property_exists($xml->document, 'bbcodes')) {
			if (isset($xml->document->bbcodes)) {
				$bbcodes = $xml->document->bbcodes[0];
			}		
			if (!$bbcodes) {
				$message .= '"bbcodes" element not found in BBCodeSet file ?? Maybe this is wrong XML file?<br />';
				$Ok = false;
			}
		}
		if ($Ok) {
			$ind1 = 0;
			foreach ($bbcodes->children() as $child) {
				if ($child->name() == 'bbcode') {
					//print_r($child);	
					$filter = trim($child->filter[0]->data());
					if (in_array($filter, $this->_filters)) {
						$alt = trim($child->name[0]->data());
						$alt = JText :: _($alt);
						$src = trim($child->file[0]->data());
						$tag = trim($child->tag[0]->data());
						if ($this->_debug) {
							$test = trim($child->test[0]->data());
						}
						if (strlen($src) > 0) {
							$posColon = strpos($src, ':');
							if (!($posColon > 0)) {
								// relative path
								if ((substr($src, 0, 1) == '/')) {
									$src = $this->_getSiteURL() . substr($src, 1);
								} else {
									// relative from $bbcodeset_path 			  	
									$src = $this->_getSiteURL() . $bbcodeset_baseurl . $src;
								}
							}
							//echo '<img src="' . $src . '" alt="' . $alt . '">';
							$skip = false;
							if (!$skip) {
								$this->_abbcode[$ind1][0] = $tag;
								$this->_abbcode[$ind1][1] = $alt;
								$this->_abbcode[$ind1][2] = $src;
								if ($this->_debug) {
									$this->_abbcode[$ind1][3] = $test;
								}
							}
							if (!$skip) {
								$ind1++;
							}
						}
					}
				}
			} // foreach
		}
		
		return $Ok;
	}

	// Show clickable BBCodes
	// $ControlID - id of target control
	// $Form - string, to which the Form code is appended
	function _renderForm($ControlID, & $Form) {
		$strOut = '';
		$strOut .= '<div class=\'yvBBCodeForm\'>';
		$src_prev = '';
		foreach ($this->_abbcode as $row) {
			$tag = $row[0];
			$tag = str_replace('\'', '\\\'', $tag);
			$alt = $row[1];
			$src = $row[2];
			;
			if (strcmp($src_prev, $src) != 0) {
				$strOut .= '<a href="javascript:void(0);" onclick="yvBBCodeReplaceText(' . $ControlID . ', \'' . $tag . '\');">';
				$strOut .= '<img src="' . $src . '" alt="' . $alt . '" />';
				$strOut .= '</a>';
				$src_prev = $src;
			}
		}
		$strOut .= '</div>';
		$Form .= $strOut;
		return true;
	}

	/* Replace codes of BBCodes with tags
	* $text - String to process  	*/
	function _replaceCode(& $text) {
		$debug = false;
		$message = '';	
	
		if ($this->_parser == null) {
			if ($debug) {
				$message .= 'BBCode parser is null<br />';
				if (!empty($message)) {
					$text .= $message;
				}										
			}	
			return;
		} 	
	
		// Don't replace BBCodes or something else in square brackes :-)
		//   inside these elements (tags):
		$tagsToExclude = array('title', 'textarea', 'input', 'script', 'select');

		$text = '*' . $text; // for strpos
		$strOut = '';
		$start1 = 0; // start offset
		$end1 = strlen($text); // end offset
		
		while ($start1 < $end1) {
			if ($debug) {
				$message .= ' start1=' . $start1 . ' end1=' . $end1 . '; ';
			}	
			//find nearest tag to exclude
			$tag3 = '';
			$start3 = $end1; 
			$end3 = $end1;
			foreach( $tagsToExclude as $tagToExclude) {
				$start4 = strpos( $text, '<' . $tagToExclude , $start1);
				if	(($start4 > 0) && ($start4 < $start3)) {
					$tag3 = $tagToExclude;
					$start3 = $start4;
				}
			} 
			if ($start3 < $end1) {
				if ($debug) {
					$message .= 'tag=' . $tag3 . ' start=' . ($start3 + strlen($tag3)) . '; ';
				}	
				//find end of element
				// 1. End of this tag
				$end4 = strpos( $text, '>', ($start3 + strlen($tag3)));
				if	(($end4 > 0) && ($end4 < $end3) && (substr($text, $end4 - 1, 1) == '/')) {
					if ($debug) {
						$message .= 'EndOfThisTag=' . $end4;
					}	
					$end3 = $end4;
				} else {
					// 2. Or closing element
					$end4 = strpos( $text, '</' . $tag3 , ($start3 + strlen($tag3)));
					if ($debug) {
						$message .= 'ClosingTag=' . $end4;
					}	
					if	(($end4 > 0) && ($end4 < $end3)) {
						$end4 = strpos($text, '>', $end4 +1);
					}
					if ($debug) {
						$message .= ' end4=' . $end4;
					}	
					if	(($end4 > 0) && ($end4 < $end3)) {
						$end3 = $end4 + 1;
					}
				}
			}
			if ($debug) {
				$message .= ' start3=' . $start3 . ' end3=' . $end3;
			}	
			
			if ($start3 > $start1) {
				// replace before tag to exclude	
				$str1 = substr($text, $start1, $start3 - $start1);
				$this->_parser->setText($str1);
				$this->_parser->parse();
				$str2 = $this->_parser->getParsed();
				$strOut .= $str2; 				
			}
			if ($start3 < $end1) {
				// don't replace here	
				$str1 = substr($text, $start3, $end3 - $start3);
				$strOut .= $str1; 				
			}
			
			$start1 = $end3;
			if ($debug) {
				$message .= '<br />';
			}	
		};
		
		// remove first character, that was added;
		$text = substr($strOut, 1);  

		if ($debug) {
			if (!empty($message)) {
				$text .= $message;
			}										
		}	
		return true;
	}

	// Is Section with this ID enabled for some operation...  
	function _articleSectionEnabled($ArticleSectionID) {
		static $sEnabled = null;
		static $aSectionID = array ();
		static $blnExclude = false;
		if ($sEnabled == null) {
			$articlesectionids = trim($this->_getConfigValue('articlesectionids', ''));
			$sEnabled = (strlen($articlesectionids) == 0);
			if (!$sEnabled) {
				$aSectionID = explode(",", $articlesectionids);
				$blnExclude = (boolean) $this->_getConfigValue('articlesectionids_exclude', '0');
			}
		}
		$Enabled = $sEnabled;
		if (!$sEnabled) {
			$blnFound = false;
			foreach ($aSectionID as $sectionid1) {
				if ((int) $sectionid1 == $ArticleSectionID) {
					$blnFound = true;
					break;
				}
			}

			if ($blnFound) {
				$Enabled = !$blnExclude;
			} else {
				$Enabled = $blnExclude;
			}
		}
		return $Enabled;
	}

	// returns value of the Extension parameter
	function _getConfigValue($paramName = '', $default = '') {
		$value = $default;

		switch ($paramName) {
			case 'access' :
				if ($this->params) {
					$value = 0;
				} else {
					// if Plugin is not loaded, then Access is denied
					$value = 0; //999; 
				}
				break;
			default :
				if ($this->params) {
					$value = $this->params->get($paramName, $default);
				}
		}
		//echo '_getConfigValue param="' . $paramName . '", value="' . $value . '"<br/>';

		return $value;
	}

	// Signature of this Extension
	function _textSignature() {
		$message = '<br/>-- <br/>' .
		'<a href="http://yurivolkov.com/Joomla/yvBBCode/index_en.html" target="_blank">' .
		'yvBBCode extension</a>, version="' .
		yvBBCodeVersion . '"';
		return $message;
	}
	
	function _getSiteURL() {
		global $mainframe;	
	  return ($mainframe->isAdmin() ? $mainframe->getSiteURL() : JURI::base());
	}

	
}
?>
